package com.baidu.iit.pxp.el.resolver;

import com.baidu.iit.pxp.el.ELContext;
import com.baidu.iit.pxp.el.PropertyNotWritableException;
import com.baidu.iit.pxp.el.juel.PropertyNotFoundException;

import java.beans.FeatureDescriptor;
import java.util.Iterator;
import java.util.List;

/**
 * User: huangweili
 * Date: 14-4-29
 * Time: 下午5:40
 */
public class ListELResolver extends ELResolver {
    private final boolean readOnly;


    public ListELResolver() {
        this(false);
    }


    public ListELResolver(boolean readOnly) {
        this.readOnly = readOnly;
    }


    @Override
    public Class<?> getCommonPropertyType(ELContext context, Object base) {
        return isResolvable(base) ? Integer.class : null;
    }


    @Override
    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
        return null;
    }


    @Override
    public Class<?> getType(ELContext context, Object base, Object property) {
        if (context == null) {
            throw new NullPointerException("context is null");
        }
        Class<?> result = null;
        if (isResolvable(base)) {
            toIndex((List<?>) base, property);
            result = Object.class;
            context.setPropertyResolved(true);
        }
        return result;
    }


    @Override
    public Object getValue(ELContext context, Object base, Object property) {
        if (context == null) {
            throw new NullPointerException("context is null");
        }
        Object result = null;
        if (isResolvable(base)) {
            int index = toIndex(null, property);
            List<?> list = (List<?>) base;
            result = index < 0 || index >= list.size() ? null : list.get(index);
            context.setPropertyResolved(true);
        }
        return result;
    }

    @Override
    public boolean isReadOnly(ELContext context, Object base, Object property) {
        if (context == null) {
            throw new NullPointerException("context is null");
        }
        if (isResolvable(base)) {
            toIndex((List<?>) base, property);
            context.setPropertyResolved(true);
        }
        return readOnly;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setValue(ELContext context, Object base, Object property, Object value) {
        if (context == null) {
            throw new NullPointerException("context is null");
        }
        if (isResolvable(base)) {
            if (readOnly) {
                throw new PropertyNotWritableException("resolver is read-only");
            }
            List list = (List) base;
            int index = toIndex(list, property);
            try {
                list.set(index, value);
            } catch (UnsupportedOperationException e) {
                throw new PropertyNotWritableException(e);
            } catch (ArrayStoreException e) {
                throw new IllegalArgumentException(e);
            }
            context.setPropertyResolved(true);
        }
    }

    private static final boolean isResolvable(Object base) {
        return base instanceof List<?>;
    }

    private static final int toIndex(List<?> base, Object property) {
        int index = 0;
        if (property instanceof Number) {
            index = ((Number) property).intValue();
        } else if (property instanceof String) {
            try {
                index = Integer.valueOf((String) property);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("Cannot parse list index: " + property);
            }
        } else if (property instanceof Character) {
            index = ((Character) property).charValue();
        } else if (property instanceof Boolean) {
            index = ((Boolean) property).booleanValue() ? 1 : 0;
        } else {
            throw new IllegalArgumentException("Cannot coerce property to list index: " + property);
        }
        if (base != null && (index < 0 || index >= base.size())) {
            throw new PropertyNotFoundException("List index out of bounds: " + index);
        }
        return index;
    }
}
